<?php

class Util
{
    public static function GMTToLocalTimestamp( $gmt_timestamp )
    {
        $diff = mktime(0, 0, 0, 1, 1, 2000) - gmmktime(0, 0, 0, 1, 1, 2000);
        return $gmt_timestamp + $diff;
    }

    public static function LocalToGMTTimestamp( $local_timestamp )
    {
        $diff = mktime(0, 0, 0, 1, 1, 2000) - gmmktime(0, 0, 0, 1, 1, 2000);
        return $local_timestamp - $diff;
    }

    public static function ArrayToXMLDocument( $xmlData )
    {
        $doc = new DOMDocument( '1.0', 'UTF-8' );
        //$doc->formatOutput = true;
        self::ArrayToXMLDocumentImpl( $xmlData, $doc );
        return $doc;
    }

    private static function ArrayToXMLDocumentImpl( $xmlData, $doc = null, $curNode = null )
    {
        if ( $curNode == null )
        {
            $curNode = $doc;
        }

        foreach ( $xmlData as $nodeName => $content )
        {
            if ( is_null($content) )
            {
                continue;
            }

            if ( is_numeric($nodeName) )
            {
                $node = $curNode;
            }
            else
            {
                $node = $doc->createElement( $nodeName );
                $curNode->appendChild( $node );
            }

            if ( is_bool( $content ) )
            {
                $content = $content ? "true" : "false";
            }

            if ( is_array( $content ) )
            {
                self::ArrayToXMLDocumentImpl( $content, $doc, $node );
            }
            else if ( !is_null($content) )
            {
                $textNode = $doc->createTextNode( "$content" );
                $node->appendChild( $textNode );
            }
        }
    }

    /**
     * The main difference from implode is that it
     * joins only non-empty values.
     *
     * @param array $pieces The array of strings to join
     */
    public static function Join($pieces, $glue = '')
    {
        if (!is_array($pieces))
        {
            return $pieces;
        }

        $result = '';

        foreach ($pieces as $piece)
        {
            if (!empty($piece))
            {
                $result .= $glue . $piece;
            }
        }

        if (!empty($result))
        {
            $result = substr($result, strlen($glue));
        }


        return $result;
    }

    public static function QuoteArray( $array )
    {
        foreach ( $array as &$value )
        {
            $value = self::Quote( $value );
        }
        return $array;
    }

    public static function QuoteParams( QueryParams $params )
    {
        self::QuoteArray( $params->GetArray() );
    }

    public static function Quote( $value )
    {
        return Core::DBConn()->Quote($value);
    }

    public static function QuoteForLikeExpression( $value )
    {
        return Core::DBConn()->QuoteForLikeExpression( $value );
    }

    public static function QuoteString( $string )
    {
        return "'" . Util::Quote($string) . "'";
    }

    public static function QuoteStringArray( $array )
    {
        foreach ( $array as &$value )
        {
            $value = self::QuoteString( $value );
        }

        return $array;
    }

    public static function GetWhereParam( $array, $operator = 'AND' )
    {
        $result = '';
        foreach ( $array as $item )
        {
            if ( !empty($result) )
            {
                $result .= " {$operator} ";
            }

            if ( is_array($item) )
            {
                $result .= '(' . self::GetWhereParam($item, 'OR') . ')';
            }
            else
            {
                $result .= $item;
            }
        }

        return $result;
    }

    public static function GetHtmlEntities( $value )
    {
        $value = htmlentities( iconv( 'UTF-8', 'UTF-8//IGNORE', $value), ENT_QUOTES, 'UTF-8');
        return $value;
    }

    public static function GetBooleanAsString( $bool )
    {
        return self::GetAsBoolean($bool) ? "true" : "false";
    }

    public static function GetAsBoolean( $bool )
    {
        return (bool)($bool && $bool !== 'false');
    }

    public static function GetFilename( $path )
    {
        $info = pathinfo($path);
        return $info['basename'];
    }

    public static function NormalizeVersion( $version, $numCount = 2 )
    {
        // ^\d+(?:\.\d+)*$
        $numCount = max( 1, min($numCount, 4) ); // $numCount = 1..4
        list($major, $minor, $maintenance, $build) = self::Split( '.', "$version", 4 );

        $major        = max(0, min(0xffff, intval($major)));
        $minor        = max(0, min(0xffff, intval($minor)));
        $maintenance  = max(0, min(0xffff, intval($maintenance)));
        $build        = max(0, min(0xffff, intval($build)));

        $nums = array($major, $minor, $maintenance, $build);
        $nums = array_slice( $nums, 0, $numCount );
        return implode('.', $nums);
    }

    public static function GetVersionNumCount( $version )
    {
        if ( preg_match('/^\d+(?:\.\d+)*$/', $version) )
        {
            return substr_count($version, ".") + 1;
        }
        return 0;
    }

    public static function CompareVersions( $v1, $v2 )
    {
        $numCount = min( self::GetVersionNumCount($v1), self::GetVersionNumCount($v2) );
        if ( $numCount == 0 )
        {
            return 0;
        }

        $v1 = self::NormalizeVersion($v1, $numCount);
        $v2 = self::NormalizeVersion($v2, $numCount);
        $v1 = preg_replace('/\.(\d+)/e', "'.'.Util::GetNumberAsString('\\1', 6)", ".$v1");
        $v2 = preg_replace('/\.(\d+)/e', "'.'.Util::GetNumberAsString('\\1', 6)", ".$v2");

        return strcmp($v1, $v2);
    }

    public static function Split( $delim, $string, $count = null )
    {
        if ( is_null($count) )
        {
            return explode($delim, $string);
        }
        else
        {
            $parts = explode($delim, $string, $count);
            if ( $count <= 0 )
            {
                return $parts;
            }

            if ( count($parts) < $count )
            {
                for ( $i = $count - count($parts); $i > 0; $i-- )
                {
                    $parts[] = '';
                }
            }
            return $parts;
        }
    }

    public static function NormalizeDate( $dateStr, $inFormat, $outFormat )
    {
        if ( empty($dateStr) )
        {
            return $dateStr;
        }

        if ( empty($inFormat) )
        {
            $inFormat = "%Y-%m-%d";
        }

        if ( empty($outFormat) )
        {
            $outFormat = "%d.%m.%Y";
        }

        return strftime( $outFormat, Util::ParseDateTime( $dateStr, $inFormat ) );
    }

    public static function ParseDateTime( $dateStr, $format )
    {
        if ( !empty($dateStr) )
        {
            if ( preg_match_all('/%[a-z]/i', $format, $matches, PREG_PATTERN_ORDER ) )
            {
                $matches = $matches[0];
                $pattern = preg_quote( $format, '/' );
                $keys = array();
                foreach ( $matches as $value )
                {
                    $pattern = str_replace($value, '(\d+)', $pattern);
                    $keys[] = $value;
                }
                $pattern = "/$pattern/";

                if ( preg_match($pattern, $dateStr, $matches ) )
                {
                    $time = array(
                        "day"   => 0,
                        "month" => 0,
                        "year"  => 0
                    );

                    reset( $matches );
                    foreach ( $keys as $key )
                    {
                        $value = intval( next( $matches ) );
                        switch ($key)
                        {
                            case '%d': $time['day']   = $value; break;
                            case '%m': $time['month'] = $value; break;
                            case '%Y': $time['year']  = $value; break;
                        }
                    }

                    return mktime( 0, 0, 0, $time['month'], $time['day'], $time['year'] );
                }
            }
        }
        return null;
    }

    public static function GetNumberAsString( $value, $digitBefore = 0, $digitAfter = 0 )
    {
        $value = floatVal( $value );
        $int = floor($value);
        $float = $value - $int;

        $value = sprintf( "%0{$digitBefore}s", $int );
        if ( $digitAfter > 0 )
        {
            $value .= substr( sprintf( "%0.{$digitAfter}f", $float ), 1 );
        }
        return $value;
    }

    public static function GetSizeInMB( $size )
    {
        return floatVal( $size ) / 1048576.0;
    }

    public static function GetSizeInKB( $size )
    {
        return  floatVal( $size ) / 1024.0;
    }

    public static function SetIfEmpty( &$var, $value )
    {
        if ( empty($var) )
        {
            $var = $value;
        }
    }

    public static function SetIfNull( &$var, $value )
    {
        if ( is_null($var) )
        {
            $var = $value;
        }
    }

    public static function SetIfEmptyString( &$var, $value )
    {
        if ( $var === '' )
        {
            $var = $value;
        }
    }

    public static function SetIfNullOrEmptyString( &$var, $value )
    {
        if ( is_null($var) || $var === '' )
        {
            $var = $value;
        }
    }

    public static function SetIfNullOrEmptyArray( &$var, $value )
    {
        if ( is_null( $var ) || count( $var ) == 0 )
        {
            $var = $value;
        }
    }

    public static function CreateXmlDocument( $xml, $throw = false )
    {
        set_error_handler('__HandleXmlError', E_WARNING);

        $exception = null;
        $doc = new DOMDocument();

        try
        {
            $doc->loadXml( $xml );
        }
        catch ( DOMException $e )
        {
            $exception = $e;
        }

        restore_error_handler();

        if ( !is_null($exception) )
        {
            $doc = null;
            if ( $throw )
            {
                throw $e;
            }
        }

        return $doc;
    }

    /**
     * Redirects to specified URL.
     *
     * @param string $url URL to redirect to
     * @param int $statusCode HTTP status code for redirection.
     *      302 Found by default
     */
    public static function Redirect($url, $statusCode = null)
    {
        if (isset($statusCode))
        {
            header("Location: $url", true, $statusCode);
        }
        else
        {
            header("Location: $url");
        }
        exit();
    }

    /**
     * Redirects to a page on the site specified by pageId.
     * Page must be defined in pages.xml.
     *
     * @param string $pageId
     * @param int $statusCode HTTP status code for redirection.
     *      302 Found by default
     */
    public static function RedirectToPage($pageId, $statusCode = null, $args = null)
    {
        $url = Pages::GetPageUrl($pageId);
        if ($url)
        {
            if (!empty($args))
            {
                $url .= ((strpos($url, '?') === false) ? '?' : '') . http_build_query($args);
            }
            Request::Redirect($url, $statusCode);
        }
    }

    public static function ParseTemplate( $templatesDir, $templateFile, $vars )
    {
        $tmpl = Core::CreateTemplateEngine( $templatesDir );
        $hash = md5($templateFile);
        $tmpl->define( $hash, $templateFile );
        $tmpl->set($vars);
        $tmpl->parse("content", $hash);
        return $tmpl->getGenerated();
    }

    public static function GetParametrizedString( $string, $vars )
    {
        $varPatterns = array();
        $values = array();
        foreach ($vars as $name => $value)
        {
            array_push($varPatterns, "%$name%");
            array_push($values, $value);
        }
        return str_replace($varPatterns, $values, $string);
    }

    public static function SetStatusCode( $code, $text = null )
    {
        if ( empty($code) )
        {
            return;
        }

        header("Status: $code $text", true);
        header("HTTP/1.0 $code $text", true);
    }

    public static function DelTree( $dirname )
    {
        if ( !file_exists($dirname) )
        {
            return false;
        }

        if ( is_file($dirname) || is_link($dirname) )
        {
            return unlink($dirname);
        }

        $dir = dir($dirname);
        while ( ($entry = $dir->read()) !== false )
        {
            if ( $entry == '.' || $entry == '..' )
            {
                continue;
            }

            self::DelTree( $dirname . DIRECTORY_SEPARATOR . $entry );
        }

        $dir->close();
        return @rmdir($dirname);
    }

    /**
     * @param string $dir - path
     * @param string $name - new folder name
     * @return bool (if folder was created returns true)
     */
    public static function CreateFolder($dir, $name)
    {
        if (is_dir($dir) && !file_exists($dir. $name))
        {
            @mkdir($dir . $name);
            return true;
        }

        return false;
    }

    /**
     *
     * Creates folder recursively
     * @param string $dir
     * @param int $mask (For *nix systems)
     */
    public static function CreateFolderRecursively( $dir, $mask = 0777 )
    {
        if ( file_exists( $dir ) )
        {
            return;
        }

        $parent = dirname( $dir );
        self::CreateFolderRecursively( $parent, $mask );

        mkdir( $dir, $mask );
    }

    /**
     * Get all entries(files and folders) from dir without recursion
     * @param string $dir - directory path
     * @param array $excludes
     * @return array - files from dir
     */
    public static function GetDirEntries($dir, $excludes = null)
    {
        $entries = array();
        if (is_dir($dir))
        {
            if (($res = opendir($dir)) !== false)
            {
                while (($entry = readdir($res)) !== false)
                {
                    if ( $entry != '..' && $entry != '.' && !self::MatchFileName($excludes, $entry) )
                    {
                        $entries[] = $entry;
                    }
                }
                closedir($res);
            }
        }
        return $entries;
    }

    public static function MatchFileName( $patterns, $filename )
    {
        $patterns = (array)$patterns;
        $useFnMatch = function_exists('fnmatch');
        foreach ($patterns as $pattern)
        {
            if (!$useFnMatch)
            {
                if ( (@preg_match('/^' . strtr(addcslashes($pattern, '/\\.+^$(){}=!<>|'),
                    array('*' => '.*', '?' => '.?')) . '$/i', $filename) ) )
                {
                    return true;
                }
            }
            else if ( fnmatch($pattern, $filename) )
            {
                return true;
            }
        }

        return false;
    }

    /**
     * Returns path relative to $compareTo, expands all '..' components.
     *
     * @param string $path Relative path
     * @param string $root Absolute or relative path
     * @return string
     */
    public static function GetPathFromRoot($path, $root)
    {
        $path = str_replace('\\', '/', $path);
        $path = trim($path, '/');
        $root = str_replace('\\', '/', $root);
        $root = rtrim($root, '/');

        // $path cannot be absolute
        if ( preg_match('/^[A-Za-z]:/', $path) )
        {
            return false;
        }

        return preg_replace('/\w+\/\.\.\//', '', $root . '/' . $path);
    }

    /**
     * Strip slashes recursively from array
     * Taken from Symfony framework
     *
     * @param mixed $value The value to strip
     *
     * @return mixed clean value with slashes stripped
     */
    public static function StripSlashesDeep($value)
    {
        return is_array($value) ? array_map(array('Util', 'StripSlashesDeep'), $value) : stripslashes($value);
    }

    /**
     * Checks MX record corresponding to host name in
     * given email address
     *
     * @param string $email Valid email address
     */
    public static function CheckMXRecord($email)
    {
        $matches = explode('@', $email);
        if (!isset($matches[1]))
        {
            return false;
        }

        $host = $matches[1];

        exec('nslookup -type=MX ' . escapeshellarg($host), $result);
        if (!empty($result))
        {
            foreach ($result as $line)
            {
                 if ( preg_match("/^$host(.*)mail exchanger = .*/", $line) )
                 {
                     return true;
                 }
            }
        }

        return false;
    }

    /**
     * Checks email validity
     *
     * @param string $email
     * @return bool
     */
    public static function IsValidEmail($email)
    {
        $isEmptyEmail = !isset( $email ) || empty( $email );
        if ( $isEmptyEmail )
        {
            return false;
        }

        $email = filter_var($email, FILTER_VALIDATE_EMAIL);
        if ( $email === false )
        {
            return false;
        }

        if ( !self::checkMXRecord( $email ) )
        {
            return false;
        }

        return true;
    }

    public static function GetArrayCorrectString($array, $value, $defaultValue = "")
    {
        return isset($array[$value]) ? $array[$value] : $defaultValue;
    }

    public static function GetCorrectString($value, $defaultValue)
    {
        return Util::GetArrayCorrectString($_GET, $value, $defaultValue);
    }

    /**
     * Returns html for select options.
     *
     * @param  array  $options An associative array of values and texts
     * @param  string $selectedValue
     * @param  bool   $addEmpty
     * @param  string $emptyText
     * @return string
     */
    public static function GetSelectOptions( $options, $selectedValue, $addEmpty = true, $emptyText = '' )
    {
        $html = '';

        if ($addEmpty)
        {
            $options = array('' => $emptyText) + $options;
        }

        foreach ($options as $value => $text)
        {
            $html .= sprintf( '<option value="%s"%s>%s</option>',
                Util::GetHtmlEntities($value),
                ( strval($selectedValue) == strval($value === false ? 0 : $value) ? ' selected="selected"' : '' ),
                Util::GetHtmlEntities($text) );
        }
        return $html;
    }

    /**
     * Возвращает код для фомирования массива в js
     *
     * @param array
     */
    public static function GetJSArray( $isArray, $depth = 0 )
    {
        if ( is_array( $isArray ) )
        {
            $res = "";
            foreach( $isArray as $key => $value )
            {
                if ( $res != "" )
                {
                    $res .= ", ";
                }
                if ( is_array( $value ) )
                {
                    $res .= '"' . $key . '":[' . self::GetJSArray( $value, $depth + 1 ) . ']';
                }
                else
                {
                    $res .= '"' . $value . '"';
                }

            }
            return "{$res}";
        }
        else
        {
            return '"' . $isArray . '"';
        }
    }

    public static function ToArrayValue( $value )
    {
        $value = trim($value);
        return !empty($value) ? array_map('trim', explode(',', $value)) : array();
    }

    public static function DeleteFiles($files, $dir)
    {
        foreach ($files as $file => $state)
        {
            if (is_dir($dir . $file))
            {
                Util::DelTree($dir . $file);
                continue;
            }
            if (file_exists($dir . $file))
            {
                unlink($dir . $file);
            }
        }
    }

    /**
     * Parses money value from string
     *
     * @param string $moneyStr
     * @return float
     */
    public static function ParseMoneyValue( $moneyStr )
    {
        return floatval( preg_replace('/[^\d\.]/u', '', $moneyStr) );
    }

    public static function UrlSafeBase64Encode($_input)
    {
        return str_replace( array('=', '+', '/'), array('_', '-', ','), base64_encode($_input) );
    }

    public static function UrlSafeBase64Decode($_input)
    {
        return base64_decode( str_replace( array('_', '-', ','), array('=', '+', '/'), $_input) );
    }

    /**
     * Invalidates resource with a given url in nginx cache
     * @param string $url
     * @param string $pathToCache path to nginx cache directory
     * @throws RuntimeException
     */
    public static function InvalidateNginxCache($url, $pathToCache)
    {
        $scriptPath = ROOT_PATH . 'misc/nginx-cache-purge';
        $cmd = 'bash %1s %2s %3s';
        $url = Request::GetHost() . '|' . $url;//deleting cache file for specific host only
        $cmd = sprintf($cmd, $scriptPath, escapeshellarg($url), $pathToCache);
        $output = shell_exec($cmd);
        if (!$output && self::IsBashSupported())
        {
            throw new RuntimeException("Couldn't execute bash command");
        }
    }

    public static function IsBashSupported()
    {
        return (shell_exec("bash -version") != null);
    }

    public static function IsFreeBsdOs()
    {
        return PHP_OS == 'FreeBSD';
    }
}

function __HandleXmlError( $errno, $errstr, $errfile, $errline )
{
    if ( strpos($errstr,"DOMDocument::loadXML()") === false )
    {
        return false;
    }
    throw new DOMException($errstr);
}

?>
